/***************************************************************************
 *   Copyright (C) 2008 by BogDan Vatra                                    *
 *   bogdan@licentia.eu                                                    *
 *                                                                         *
 *   This program is free software: you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation, either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/
#include <QApplication>
#include <QDir>
#include <QPluginLoader>
#include <QDomDocument>
#include <QSettings>
#include <QSqlError>
#include <QMessageBox>
#include <QDebug>

#include "baseinterface.h"
#include "menuwidgetinterface.h"
#include "addwidetinterface.h"
#include "kontamabilloader.h"
#include "kontamabilwindow.h"
#include "database.h"
#include "query.h"

using namespace SQL;
using namespace KontAMabil;

inline void initMyResource()
{
	Q_INIT_RESOURCE(kontAMabilSQL);
}


KontAMabilLoader * kontAMabil(KontAMabilLoader * loader)
{
	static KontAMabilLoader * k=0;
	if (loader)
		k=loader;
	if (!k)
		k=new KontAMabilLoader;
	return k;
}

KontAMabilLoader::KontAMabilLoader()
{
	initMyResource();
}

KontAMabilLoader::~KontAMabilLoader()
{
	foreach (KontAMabilWindow* w, m_showedBasePlugin)
		delete w;
}

bool KontAMabilLoader::compPlugins(BaseInterface* p1, BaseInterface* p2)
{
	return p1->order() < p2->order();

}

bool KontAMabilLoader::compMenuPlugins(MenuWidgetInterface* p1, MenuWidgetInterface* p2)
{
	if (p1->menuGroup() == p2->menuGroup())
		return p1->order() < p2->order();
	return p1->menuGroup() < p2->menuGroup();
}

const QList<BaseInterface*> & KontAMabilLoader::basePlugins()
{
	return m_base;
}

const QList<MenuWidgetInterface*> & KontAMabilLoader::menuPlugins()
{
	return m_menus;
}


void KontAMabilLoader::loadPlugins(QSplashScreen *splash)
{
	QDir pluginsDir = QDir(qApp->applicationDirPath());
#if defined(Q_OS_WIN)
	if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release")
		pluginsDir.cdUp();
#elif defined(Q_OS_MAC)
	if (pluginsDir.dirName() == "MacOS")
	{
		pluginsDir.cdUp();
		pluginsDir.cdUp();
		pluginsDir.cdUp();
	}
#else
	pluginsDir.cd("../lib");
#endif
	pluginsDir.cd("kontAMabil");

	foreach(QString fileName, pluginsDir.entryList(QDir::Files))
	{
		if (splash)
			splash->showMessage(fileName,Qt::AlignLeft,Qt::white);
		qApp->processEvents();

		QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
		loader.setLoadHints(QLibrary::ResolveAllSymbolsHint|QLibrary::ExportExternalSymbolsHint);
		QObject *plugin = loader.instance();

		if (plugin && dynamic_cast<BaseInterface*>(plugin))
		{
			if (dynamic_cast<MenuWidgetInterface*>(plugin))
				m_menus.push_back(dynamic_cast<MenuWidgetInterface*>(plugin));
			else
				m_base.push_back(dynamic_cast<BaseInterface*>(plugin));

			dynamic_cast<BaseInterface*>(plugin)->setKontAMabil(this);
			dynamic_cast<BaseInterface*>(plugin)->setDatabase(defaultDatabase());
		}
		else
			QMessageBox::information ( 0, fileName, loader.errorString());
	}

	if (m_base.size())
		qSort(m_base.begin(), m_base.end(), compPlugins);

	if (m_menus.size())
		qSort(m_menus.begin(), m_menus.end(), compMenuPlugins);

	for (int i=0;i<m_base.size();i++)
		if (!addPlugin(m_base[i]))
			m_base.removeAt(i--);

	for (int i=0;i<m_menus.size();i++)
		if (!addPlugin(m_menus[i]))
			m_menus.removeAt(i--);

}

void KontAMabilLoader::prepareQuerys(const QString & query, const QString & pluginName)
{

	QDomDocument doc;
	if (!doc.setContent(query))
	{
		qCritical() << QWidget::tr("Nu pot procesa");
		qCritical() << query;
		qCritical() << pluginName;
		return;
	}

	QSqlQuery qtr(defaultDatabase()->database());
	qtr.exec("BEGIN");
	int err=LoadError::NO_ERROR;

	QDomElement versions = doc.documentElement().firstChildElement("versions");
	qreal version = 0;
	qreal pluginVer = 0;
	if (qry->locate("denumire", pluginName,Query::SortedSearch))
		version = qry->value("versiune").toDouble();
	else
	{
		qry->insert();
		qry->setValue("denumire",pluginName);
		qry->setValue("versiune",version);
		qry->post();
	}

	QDomElement ver = versions.firstChildElement();
	while (!ver.isNull())
	{
		pluginVer = ver.attribute("value", "0").toDouble();
		if (pluginVer > version)
		{
			QSqlQuery qv(defaultDatabase()->database());
			qv.prepare("select * from exec_signed_sql(:signature, :sql, :execunsafe)");
			qv.bindValue(":signature", ver.firstChildElement("signature").text());
			qv.bindValue(":sql", ver.firstChildElement("sql").text());
			qv.bindValue(":execunsafe", m_execUnsafeSql);
			if (!qv.exec())
			{
				LoadError er;
				er.pluginName = pluginName;
				er.errorMsg = qv.lastError().text();
				er.errorNo = LoadError::VERSION_UPDATE_ERROR;
				err|=er.errorNo;
				er.version = pluginVer;
				m_errors.push_back(er);
				qCritical()<<qv.lastError().text();
			}
		}
		ver = ver.nextSiblingElement();
	}

	if (pluginVer < version)
	{
		LoadError er;
		er.pluginName = pluginName;
		er.errorMsg = QWidget::tr("Modulul %1 trebuie actualizat.").arg(pluginName);
		er.errorNo = LoadError::PLUNGIN_NEED_UPDATE;
		er.version = version;
		err|=er.errorNo;
		m_errors.push_back(er);
	}
	qry->update();
	qry->setValue("versiune",pluginVer);
	qry->post();

	QDomElement docElem = doc.documentElement().firstChildElement("query");
	while (!docElem.isNull())
	{
		Query * q = new Query;
		q->setObjectName(docElem.attribute("name", "unnamed_query").trimmed());
		if (docElem.hasAttribute("primary_key"))
			q->setPrimaryKey(docElem.attribute("primary_key", ""));

		if (docElem.hasAttribute("privileges_table"))
			q->setPrivilegesForTable(docElem.attribute("privileges_table", ""));

		if (docElem.hasAttribute("listen_notify"))
			q->setListenNotifyMessage(docElem.attribute("listen_notify", ""));

		QDomElement prop = docElem.firstChildElement();
		while (!prop.isNull())
		{
			if (prop.tagName() == "select_sql")
				q->setSelectQuery(prop.text());

			if (prop.tagName() == "insert_sql")
				q->setInsertQuery(prop.text());

			if (prop.tagName() == "update_sql")
				q->setUpdateQuery(prop.text());

			if (prop.tagName() == "remove_sql")
				q->setRemoveQuery(prop.text());

			if (prop.tagName() == "refresh_sql")
				q->setRefreshQuery(prop.text());

			if (prop.tagName() == "order_fields")
			{
				QDomElement ord = prop.firstChildElement();
				QStringList of;
				while (!ord.isNull())
				{
					of << ord.text();
					ord = ord.nextSiblingElement();
				}
				q->setOrderFields(of);
			}
			prop = prop.nextSiblingElement();
		}

		if (docElem.hasAttribute("exec_select_on_create"))
			if (docElem.attribute("exec_select_on_create", "") == "true")
			{
				try
				{
					q->select();
				}
				catch (const QString & error)
				{
					qCritical()<<error;
					LoadError er;
					er.pluginName = pluginName;
					er.errorMsg = error;
					er.errorNo = LoadError::SELECT_SQL_ERROR;
					er.version = 0;
					err|=er.errorNo;
					m_errors.push_back(er);
				}
				catch (...)
				{
					LoadError er;
					er.pluginName = pluginName;
					er.errorMsg = "unknown error";
					er.errorNo = LoadError::UNKNOWN_ERROR;
					er.version = 0;
					err|=er.errorNo;
					m_errors.push_back(er);
				}
			}
		docElem = docElem.nextSiblingElement("query");
	}
	if (err!=LoadError::NO_ERROR)
		qtr.exec("ROLLBACK");
	else
		qtr.exec("COMMIT");
}

bool KontAMabilLoader::loadQuerys(QSplashScreen *splash)
{
	QSqlQuery qp(defaultDatabase()->database());
	qp.exec("select count(*) from pg_tables where schemaname='public' and tablename='plugins'");
	qp.first();
	if (!qp.value(0).toInt())
	{
		qp.exec("CREATE TRUSTED PROCEDURAL LANGUAGE 'plperl' HANDLER plperl_call_handler VALIDATOR plperl_validator");
		qp.exec("CREATE PROCEDURAL LANGUAGE 'plperlu' HANDLER plperl_call_handler VALIDATOR plperl_validator");
		qp.exec("CREATE TRUSTED PROCEDURAL LANGUAGE 'plpgsql' HANDLER plpgsql_call_handler VALIDATOR plpgsql_validator");
		QFile f(":/kontAMabilSQL/sql/initializare.sql");
		if (f.open(QIODevice::ReadOnly))
		{
			qp.exec(f.readAll());
			f.close();
		}
	}
	qry = new Query;
	qry->setSelectQuery("select * from plugins order by denumire");
	qry->setInsertQuery("insert into plugins (denumire, versiune) values (:denumire, :versiune) returning denumire");
	qry->setUpdateQuery("update plugins set versiune=:versiune where denumire=:denumire returning denumire");
	qry->setRefreshQuery("select * from plugins where denumire=:denumire");
	qry->setOrderFields(QStringList() << "denumire");
	qry->setPrimaryKey("denumire");
	qry->select();
	QSettings s;
	m_execUnsafeSql = s.value("aplicatie/execunsafesql", "true").toBool();
	foreach(BaseInterface* base, m_querys)
	{
		if (splash)
			splash->showMessage(QWidget::tr("Prepar sql pentru %1").arg(base->name()),Qt::AlignLeft,Qt::white);
		qApp->processEvents();

		if (base->querys().size())
			prepareQuerys(base->querys(), base->name());
	}
	delete qry;

	foreach (LoadError err, m_errors)
		qCritical()<<err.pluginName<<err.errorMsg;

	return !m_errors.size();
}

const QList<LoadError> & KontAMabilLoader::errors()
{
	return m_errors;
}

int KontAMabilLoader::compVersion(const QString & ver1, const QString & ver2)
{
	QStringList v1=ver1.split('.');
	QStringList v2=ver2.split('.');
	for (int i=0;i<v1.size() && i<v2.size();i++)
	{
		bool ok1;
		int val1=v1[i].toInt(&ok1);

		bool ok2;
		int val2=v2[i].toInt(&ok2);
		if (ok1 && ok2)
		{
			if (val1-val2)
				return val1-val2;
		}
		else
			if (v1[i].compare(v2[i]))
				return v1[i].compare(v2[i]);
	}
	return 0;
}


QList<KontAMabilLoader::__versions__> KontAMabilLoader::computeVersions(const QString & versions)
{
	Q_UNUSED(versions);
	QList<__versions__> vers;
#warning TODO Versiuni !!!!
	return vers;
}

QList<KontAMabilLoader::__plugin__> KontAMabilLoader::dependencies(KontAMabil::BaseInterface* plugin)
{
	QList <__plugin__> deps;
	QStringList depnames=plugin->depends().split(';');
	if (!depnames.size())
		return deps;

	foreach(QString plug, depnames)
	{
		if (!plug.trimmed().length())
			continue;
		__plugin__ p;
		p.name=plug.trimmed().toLower();
		p.versions=computeVersions(plug.trimmed().toLower());
		deps.push_back(p);
	}
	return deps;
}

bool KontAMabilLoader::addPlugin(KontAMabil::BaseInterface* plugin)
{
	foreach(KontAMabil::BaseInterface*plug, m_querys)
		if (plug==plugin)
			return true;


	foreach(__plugin__ p ,dependencies(plugin))
	{
		bool find=false;
		foreach(BaseInterface* base, m_base)
			if (base->name().trimmed().toLower()==p.name)
			{
				find=addPlugin(base);
				if (!find)
				{
					QMessageBox::critical( 0, "Eroare incarcare modul", QString("Modulul \"%1\" nu poate fi incarcat deoarece depinde de modulul \"%2\" care nu a fost incarcat.").arg(plugin->name()).arg(base->name()));
					return false;
				}
			}

		if (find)
			continue;

		foreach(MenuWidgetInterface* base, m_menus)
			if (base->name().trimmed().toLower()==p.name)
			{
				find=addPlugin(base);
				if (!find)
				{
					QMessageBox::critical( 0, "Eroare incarcare modul", QString("Modulul \"%1\" nu poate fi incarcat deoarece depinde de modulul \"%2\" care nu a fost incarcat.").arg(plugin->name()).arg(base->name()));
					return false;
				}
			}

		if (!find)
		{
			QMessageBox::critical( 0, "Eroare incarcare modul", QString("Modulul \"%1\" nu poate fi incarcat deoarece depinde de modulul \"%2\" care nu a fost gasit.").arg(plugin->name()).arg(p.name));
			return false;
		}
	}
	m_querys.push_back(plugin);
	return true;
}

KontAMabilWindow* KontAMabilLoader::showNextBasePlugin()
{
	if (m_base.size()<=m_showedBasePlugin.size())
		return 0;
	KontAMabilWindow *window=m_base[m_showedBasePlugin.size()]->createWidget();
	QObject::connect(window, SIGNAL(aboutToReject()), window, SLOT(close()));
	m_showedBasePlugin.push_back(window);
	window->show();
	return window;
}

void KontAMabilLoader::addGlobalValue(const QString & valueName, const QVariant & val )
{
	m_globalValues[valueName]=val;
}

QVariant KontAMabilLoader::globalValue(const QString & valueName)
{
	return m_globalValues[valueName];
}

const QMap<QString,QVariant> & KontAMabilLoader::globalValues()
{
	return m_globalValues;
}

